<template>
  <!-- 表单 -->
  <div :class="{ 'box-e': border }">
    <el-form
      ref="baseForm"
      :model="formData"
      :inline="inline"
      :label-width="labelWidth"
      :size="size"
      :disabled="disabled"
      :label-position="labelPosition"
      border
    >
      <!-- 行内排列 -->
      <template v-if="inline">
        <!-- 表单项 -->
        <template v-for="(formItem, index) in iFormList" :key="index">
          <el-form-item
            :label-width="formItem.labelWidth"
            :prop="formItem.key"
            :rules="formItem.rules"
            v-show="!formItem.hidden"
          >
            <!-- 标题 -->
            <template #label>
              {{ formItem.label }}
            </template>
            <!-- 插槽 -->
            <template v-if="formItem.tag === 'slot'">
              <slot :name="formItem.key" :row="formData">
                {{
                  formItem.filter
                    ? formItem.filter(formData)
                    : formItem.key
                      ? formData[formItem.key]
                      : ""
                }}
              </slot>
            </template>
            <!-- 单选组件 -->
            <el-radio-group
              v-else-if="formItem.tag === 'el-radio-group'"
              v-model="formData[formItem.key!]"
              v-bind="formItem.props"
            >
              <el-radio
                v-for="opt in formItem.options"
                :key="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
                :label="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
              >
                {{ opt[(formItem.optionProps && formItem.optionProps.name) || "name"] }}
              </el-radio>
            </el-radio-group>
            <!-- 其它表单组件 -->
            <component
              v-else
              :is="formItem.tag"
              v-model="formData[formItem.key!]"
              v-bind="formItem.props"
            >
              <template v-if="formItem.tag === 'el-checkbox-group'">
                <el-checkbox
                  v-for="opt in formItem.options"
                  :key="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
                  :label="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
                >
                  {{ opt[(formItem.optionProps && formItem.optionProps.name) || "name"] }}
                </el-checkbox>
              </template>
              <template v-if="formItem.tag === 'el-select'">
                <el-option
                  v-for="opt in formItem.options"
                  :key="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
                  :label="opt[(formItem.optionProps && formItem.optionProps.name) || 'name']"
                  :value="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
                >
                </el-option>
              </template>
            </component>
            <!-- 表单项提示框 -->
            <el-popover
              v-if="formItem.tips"
              class="tips-pop"
              placement="top-start"
              title="提示"
              width="320"
              trigger="hover"
              :content="formItem.tips"
            >
              <i class="el-icon-question tips-icon" slot="reference"></i>
            </el-popover>
          </el-form-item>
          <el-button
            type="success"
            size="small"
            v-if="formItem.copy"
            @click="copy(formData[formItem.key!])"
            >复制</el-button
          >
        </template>
        <div style="display: flex; justify-content: flex-end; width: 100%">
          <slot name="button"></slot>
        </div>
      </template>
      <!-- 栅格排列 -->
      <el-row :gutter="20" v-else>
        <template v-for="(formItem, index) in iFormList" :key="index">
          <el-col :span="formItem.span">
            <el-row :gutter="15">
              <el-col :span="formItem.copy ? 20 : 24">
                <template v-if="formItem.tag === 'title'">
                  <div class="title" v-bind="formItem.props">
                    <span v-html="formItem.label"></span>
                  </div>
                </template>
                <!-- 表单项 -->
                <el-form-item
                  v-else
                  :label-width="formItem.labelWidth"
                  :prop="formItem.key"
                  :rules="formItem.rules"
                  v-show="!formItem.hidden"
                >
                  <!-- 标题 -->
                  <template #label>
                    {{ formItem.label }}
                  </template>
                  <!-- 插槽 -->
                  <template v-if="formItem.tag === 'slot'">
                    <slot :name="formItem.key" :row="formData">
                      {{ formItem.filter ? formItem.filter(formData) : formData[formItem.key!] }}
                    </slot>
                  </template>
                  <div class="text" v-else-if="formItem.tag === 'text'">
                    {{ formData[formItem.key!] }}
                  </div>
                  <div class="text" v-else-if="formItem.tag === 'selectText'">
                    {{
                      formItem.options!.find((item) => item.id === formData[formItem.key!])?.name
                    }}
                  </div>
                  <el-cascader
                    v-else-if="formItem.tag === 'el-cascader'"
                    v-model="formData[formItem.key!]"
                    :options="formItem.options"
                    v-bind="formItem.props"
                  ></el-cascader>
                  <!-- 数字选择框 -->
                  <el-input-number
                    v-else-if="formItem.tag === 'el-input-number'"
                    v-model="formData[formItem.key!]"
                    v-bind="formItem.props"
                  ></el-input-number>
                  <!-- 单选组件 -->
                  <el-radio-group
                    v-else-if="formItem.tag === 'el-radio-group'"
                    v-model="formData[formItem.key!]"
                    v-bind="formItem.props"
                  >
                    <el-radio
                      v-for="opt in formItem.options"
                      :key="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
                      :label="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
                    >
                      {{ opt[(formItem.optionProps && formItem.optionProps.name) || "name"] }}
                    </el-radio>
                  </el-radio-group>
                  <!-- 其它表单组件 -->
                  <component
                    v-else
                    style="width: 100%"
                    :is="formItem.tag"
                    v-model="formData[formItem.key!]"
                    v-bind="formItem.props"
                  >
                    <template v-if="formItem.tag === 'el-checkbox-group'">
                      <el-checkbox
                        v-for="opt in formItem.options"
                        :key="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
                        :label="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
                      >
                        {{ opt[(formItem.optionProps && formItem.optionProps.name) || "name"] }}
                      </el-checkbox>
                    </template>
                    <template v-if="formItem.tag === 'el-select'">
                      <el-option
                        v-for="opt in formItem.options"
                        :key="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
                        :label="opt[(formItem.optionProps && formItem.optionProps.name) || 'name']"
                        :value="opt[(formItem.optionProps && formItem.optionProps.id) || 'id']"
                      >
                      </el-option>
                    </template>
                  </component>
                  <!-- 表单项提示框 -->
                  <el-popover
                    v-if="formItem.tips"
                    class="tips-pop"
                    placement="top-start"
                    title="提示"
                    width="320"
                    trigger="hover"
                    :content="formItem.tips"
                  >
                    <i class="el-icon-question tips-icon" slot="reference"></i>
                  </el-popover>
                </el-form-item>
              </el-col>
              <el-col :span="2" v-if="formItem.copy">
                <el-button type="primary" plain :size="size" @click="copy(formData[formItem.key!])"
                  >复制</el-button
                >
              </el-col>
            </el-row>
          </el-col>
        </template>
      </el-row>
    </el-form>
  </div>
</template>

<script setup lang="ts">
import { onMounted, reactive, watch, defineProps, ref, defineExpose } from "vue";
import useClipboard from "vue-clipboard3";
import { ElMessage, type FormInstance } from "element-plus";

// 定义表单项接口
interface OptionProps {
  id?: string;
  name?: string;
}

interface FormItem {
  key?: string;
  tag: string;
  label?: string;
  span?: number;
  props?: Record<string, any>;
  options?: Array<any>;
  optionProps?: OptionProps;
  rules?: Array<any>;
  hidden?: boolean;
  copy?: boolean;
  filter?: (data: any) => any;
  labelWidth?: string;
  default?: any;
  tips?: string;
  vshow?: boolean;
}

// 定义 props 类型
interface BaseFormProps {
  formList: FormItem[];
  formData: Record<string, any>;
  inline?: boolean;
  labelWidth?: string;
  size?: any;
  disabled?: boolean;
  labelPosition?: any;
  border?: boolean;
}

// 复制
const { toClipboard } = useClipboard();

const props = withDefaults(defineProps<BaseFormProps>(), {
  inline: false,
  labelWidth: "",
  size: "large",
  disabled: false,
  labelPosition: "top",
  border: true,
  formList: () => [],
  formData: () => ({}),
});

// 表单配置
const iFormList = ref<FormItem[]>([]);
// 表单对象节点
const baseForm = ref<FormInstance | null>(null);
// 接口获取所有选项数据
const options = reactive<Record<string, any>>({});

// 复制
async function copy(e: string) {
  try {
    await toClipboard(e);
    ElMessage.success("复制成功");
  } catch (error) {
    ElMessage.error("复制失败");
  }
}

// 监听
watch(
  () => props.formList,
  () => {
    initFormList();
  },
  { deep: true },
);

// 监听
watch(
  () => props.formData,
  () => {
    initFormList();
  },
  { deep: true },
);

// 监听
watch(
  options,
  () => {
    initFormList();
  },
  { deep: true },
);

onMounted(() => {
  initFormList();
});

// 初始化iFormList
function initFormList() {
  const { formList, formData } = props;
  iFormList.value = formList.map((item) => {
    const newItem: FormItem = deepClone(item);
    if (item.key && !(item.key in formData)) {
      if ("default" in item) {
        formData[item.key] = item.default;
      } else if (!["slot", "title"].includes(item.tag)) {
        formData[item.key] = "";
      }
    }
    newItem.vshow = true;
    if (item.vshow === false) {
      newItem.vshow = false;
    }
    if (item.options && typeof item.options === "function") {
      delete (newItem as any).options;
    }

    return newItem;
  });
}

// 深拷贝函数
function deepClone<T>(obj: T): T {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  if (Array.isArray(obj)) {
    const newObj = [] as any[];
    obj.forEach((item) => {
      newObj.push(deepClone(item));
    });
    return newObj as unknown as T;
  }

  const newObj = {} as Record<string, any>;
  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      newObj[key] = deepClone((obj as Record<string, any>)[key]);
    }
  }
  return newObj as unknown as T;
}

// 清空表单
const resetForm = () => {
  if (baseForm.value) {
    baseForm.value.resetFields();
  }
};

// 验证表单
const checkValidate = (): Promise<boolean> => {
  return new Promise((resolve, reject) => {
    if (baseForm.value) {
      baseForm.value.validate((valid) => {
        if (valid) {
          resolve(true);
        } else {
          reject(false);
        }
      });
    } else {
      reject(false);
    }
  });
};

// 暴露方法
defineExpose({
  resetForm,
  checkValidate,
});
</script>

<style lang="scss" scoped>
.title {
  font-size: 18px;
  font-weight: bold;
  margin-bottom: 20px;
}
::v-deep {
  .el-select--large .el-select__wrapper {
    width: 200px;
  }
}
</style>
